﻿using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;

using System.Collections;
using System.Collections.Generic;

using xEngine.UI;
using xEngine.Actors;

namespace xEngine.Rendering
{
    public class RenderManager
    {
        #region Singleton

        private static RenderManager inst = new RenderManager();
        private RenderManager()
        {
            _renderTargets = new Stack<RenderTarget2D>();
            _matrices = new Stack<Matrix>();
            WorldMatrix = Matrix.Identity;
        }

        #endregion

        #region Member Variables

        private XRenderer _renderer;
        private ContentManager _content;
        private Stack<RenderTarget2D> _renderTargets;

        private Stack<Matrix> _matrices;

        #endregion

        #region Properties

        public static ContentManager EmbeddedContent { get { return inst._content; } }
        public static GraphicsDevice Device { get { return inst._renderer.GraphicsDevice; } }

        public static Matrix WorldMatrix { get; private set; }

        #endregion

        #region Functions

        public static void Initialize(XRenderer renderer)
        {
            inst._renderer = renderer;
            inst._content = new ResourceContentManager(inst._renderer.Services, Resources.ResourceManager);
        }

        public static void PushRenderTarget(RenderTarget2D rt)
        {
            PushRenderTarget(rt, Color.Transparent);
        }
        public static void PushRenderTarget(RenderTarget2D rt, Color color)
        {
            PushRenderTarget(rt, true, color);
        }
        public static void PushRenderTargetNoClear(RenderTarget2D rt)
        {
            PushRenderTarget(rt, false, Color.Transparent);
        }
        private static void PushRenderTarget(RenderTarget2D rt, bool clear, Color color)
        {
            if (Device != null)
            {
                inst._renderTargets.Push(Device.GetRenderTargets().Length > 0 ? (RenderTarget2D)Device.GetRenderTargets()[0].RenderTarget : null);
                Device.SetRenderTarget(rt);

                if (clear)
                {
                    if(rt.DepthStencilFormat != DepthFormat.None)
                        Device.Clear(ClearOptions.DepthBuffer | ClearOptions.Target, color, 1, 0);
                    else
                        Device.Clear(ClearOptions.Target, color, 1, 0);

                }
                
                Device.DepthStencilState = DepthStencilState.Default;
                Device.BlendFactor = Color.Transparent;
                Device.BlendState = BlendState.Opaque;
            }
        }
        public static void PopRenderTarget()
        {
            if (Device == null)
                return;

            if (inst._renderTargets.Count > 0)
                Device.SetRenderTarget(inst._renderTargets.Pop());
            else
                Device.SetRenderTarget(null);
        }

        public static void PushMatrix(Matrix m)
        {
            inst._matrices.Push(m);
            WorldMatrix *= m;
        }
        public static void PopMatrix()
        {
            inst._matrices.Pop();
            WorldMatrix = Matrix.Identity;
            foreach (Matrix m in inst._matrices)
                WorldMatrix *= m;
        }
        public static void ResetMatrix()
        {
            WorldMatrix = Matrix.Identity;
            inst._matrices.Clear();
        }

        public static Ray ProjectRayFromScreenPoint(Point p, Camera c)
        {
            return ProjectRayFromScreenPoint(p.X, p.Y, c);
        }
        public static Ray ProjectRayFromScreenPoint(System.Drawing.Point p, Camera c)
        {
            return ProjectRayFromScreenPoint(p.X, p.Y, c);
        }
        public static Ray ProjectRayFromScreenPoint(int x, int y, Camera c)
        {
            Vector3 s = inst._renderer.GraphicsDevice.Viewport.Unproject(new Vector3(x, y, 0), c.Projection, c.View, Matrix.Identity);
            return new Ray(s, Vector3.Normalize(inst._renderer.GraphicsDevice.Viewport.Unproject(new Vector3(x, y, 1), c.Projection, c.View, Matrix.Identity) - s));
        }

        #endregion
    }
}
